iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
Modern Web

React TDD 實戰:用 Vitest 打造可靠的前端應用系列 第 12

Day 12 - 基本符號轉換(1-10) 🔢

  • 分享至 

  • xImage
  •  

昨天成功處理了 1、2、3,但 4 輸出 "IIII" 而非 "IV"。今天用 TDD 處理羅馬數字的減法規則。

羅馬數字減法規則 ⚖️

核心規則

  • 小數字在大數字前面時做減法
  • IV = V - I = 5 - 1 = 4
  • IX = X - I = 10 - 1 = 9

處理數字 4:第一個減法規則 🎯

建立 tests/day12/roman-numerals.test.ts

import { describe, it, expect } from 'vitest'
import { toRoman } from '../../src/roman/romanNumerals'

describe('Roman Numerals Converter', () => {
  it('converts 1 to I', () => {
    expect(toRoman(1)).toBe('I')
  })
  it('converts 2 to II', () => {
    expect(toRoman(2)).toBe('II')
  })
  it('converts 3 to III', () => {
    expect(toRoman(3)).toBe('III')
  })
  it('converts 4 to IV', () => {
    expect(toRoman(4)).toBe('IV')
  })
})

測試失敗 🔴:Expected: "IV", Received: "IIII"

更新 src/roman/romanNumerals.ts

export function toRoman(num: number): string {
  if (num === 4) return 'IV'
  
  let result = ''
  for (let i = 0; i < num; i++) {
    result += 'I'
  }
  return result
}

測試通過! 🟢

處理數字 5:V 的登場 ✨

加入測試:

it('converts 5 to V', () => {
  expect(toRoman(5)).toBe('V')
})

修改實作:

export function toRoman(num: number): string {
  if (num === 5) return 'V'
  if (num === 4) return 'IV'
  
  let result = ''
  for (let i = 0; i < num; i++) {
    result += 'I'
  }
  return result
}

處理 6-8:V 系列測試 📈

加入測試:

it('converts 6 to VI', () => {
  expect(toRoman(6)).toBe('VI')
})
it('converts 7 to VII', () => {
  expect(toRoman(7)).toBe('VII')
})
it('converts 8 to VIII', () => {
  expect(toRoman(8)).toBe('VIII')
})

重構 實作:

export function toRoman(num: number): string {
  let result = ''
  
  if (num === 4) {
    return 'IV'
  }
  
  if (num >= 5) {
    result += 'V'
    num -= 5
  }
  
  for (let i = 0; i < num; i++) {
    result += 'I'
  }
  
  return result
}

測試通過! ✅

挑戰數字 9:第二個減法規則 🎪

加入測試:

it('converts 9 to IX', () => {
  expect(toRoman(9)).toBe('IX')
})

測試失敗 🔴:輸出 "VIIII" 而非 "IX"

更新 實作:

export function toRoman(num: number): string {
  let result = ''
  
  if (num === 9) return 'IX'
  if (num === 4) return 'IV'
  
  if (num >= 5) {
    result += 'V'
    num -= 5
  }
  
  for (let i = 0; i < num; i++) {
    result += 'I'
  }
  
  return result
}

處理數字 10 🔟

加入測試:

it('converts 10 to X', () => {
  expect(toRoman(10)).toBe('X')
})

更新 實作:

export function toRoman(num: number): string {
  let result = ''
  
  if (num === 10) return 'X'
  if (num === 9) return 'IX'
  if (num === 4) return 'IV'
  
  if (num >= 5) {
    result += 'V'
    num -= 5
  }
  
  for (let i = 0; i < num; i++) {
    result += 'I'
  }
  
  return result
}

測試通過! 🎉

最終重構:映射表模式 🎨

重構 成映射表形式:

export function toRoman(num: number): string {
  const mappings = [
    [10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I']
  ]
  
  let result = ''
  
  for (const [value, symbol] of mappings) {
    while (num >= value) {
      result += symbol
      num -= value
    }
  }
  
  return result
}

TDD 學習重點 💡

透過 TDD,我們發現羅馬數字的重要模式:

  • 大數字優先:先處理大數字(10 → 5 → 1)
  • 減法規則優先:4 和 9 要特別處理
  • 貪婪算法:每次選擇最大可能符號

完整程式碼 📋

完整實作 src/roman/romanNumerals.ts

export function toRoman(num: number): string {
  const mappings = [
    [10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I']
  ]
  
  let result = ''
  
  for (const [value, symbol] of mappings) {
    while (num >= value) {
      result += symbol
      num -= value
    }
  }
  
  return result
}

完整測試 tests/day12/roman-numerals.test.ts

import { describe, it, expect } from 'vitest'
import { toRoman } from '../../src/roman/romanNumerals'

describe('Roman Numerals Converter', () => {
  const testCases = [
    [1, 'I'], [2, 'II'], [3, 'III'], [4, 'IV'], [5, 'V'],
    [6, 'VI'], [7, 'VII'], [8, 'VIII'], [9, 'IX'], [10, 'X']
  ]
  
  testCases.forEach(([num, expected]) => {
    it(`converts ${num} to ${expected}`, () => {
      expect(toRoman(num)).toBe(expected)
    })
  })
})

測試驗證 ✅

執行測試:

npm test tests/day12

所有測試通過!我們有了處理 1-10 的羅馬數字轉換器。

今天學到的重點 📝

  • 減法規則:IV = 4, IX = 9
  • TDD 循環:紅-綠-重構節奏
  • 映射表模式:簡化條件邏輯
  • 大數字優先:貪婪算法概念
  • 測試驅動設計:讓測試指導實作

今日小挑戰 🏆

試著思考以下問題:

  1. 為什麼選擇映射表模式?有什麼優點?
  2. 如果要處理 0 或負數,該如何修改?
  3. 映射表的順序為什麼重要?

重點回顧 🎁

今天我們成功實作了 1-10 的羅馬數字轉換,從最簡單的 if-else 開始,逐步演進到優雅的映射表模式。這就是 TDD 的魅力 - 讓我們的程式碼隨著測試逐步進化。

每個測試都像是一個小小的里程碑,紅燈告訴我們方向,綠燈確認我們走對了路,重構讓我們的程式碼更加優雅。這就是 TDD 的節奏感!

今天的 TDD 之旅就到這裡,明天我們繼續深入探索! 🚀


上一篇
Day 11 - Kata 介紹與設置 🎯
下一篇
Day 13 - 擴展到百位數(11-100)
系列文
React TDD 實戰:用 Vitest 打造可靠的前端應用28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言